﻿using System;
using System.Globalization;
using System.Net;
using System.Threading;
using Golos.Helper;

namespace Golos.Http
{
    public class HttpServerBase : IDisposable
    {
        public event EventHandler<HttpHandler> IncomingRequest = null;
        
        public enum State
        {
            Stopped,
            Stopping,
            Starting,
            Started
        }

        public HttpServerBase(int port)
        {
            if (!HttpListener.IsSupported)
            {
                LogClass.WriteErrorEntry(new NotSupportedException("The HttpListener class is not supported on this operating system.").ToString());
                return;
            }
            httpListener = new HttpListener();
            httpListener.Prefixes.Add(string.Format("http://{0}:{1}/", "*", port));
        }

        ~HttpServerBase()
        {
            this.Dispose(false);
        }

        public State RunState
        {
            get
            {
                return (State)Interlocked.Read(ref _runState);
            }
        }

        public virtual void Start()
        {
            if (this._connectionManagerThread == null || this._connectionManagerThread.ThreadState == ThreadState.Stopped)
            {
                this._connectionManagerThread = new Thread(new ThreadStart(this.ConnectionManagerThreadStart));
                this._connectionManagerThread.Name = String.Format(CultureInfo.InvariantCulture, "ConnectionManager_{0}", Guid.NewGuid());
            }
            else if (this._connectionManagerThread.ThreadState == ThreadState.Running)
            {
                throw new ThreadStateException("The request handling process is already running.");
            }

            if (this._connectionManagerThread.ThreadState != ThreadState.Unstarted)
            {
                throw new ThreadStateException("The request handling process was not properly initialized so it could not be started.");
            }
            this._connectionManagerThread.Start();

            long waitTime = DateTime.Now.Ticks + TimeSpan.TicksPerSecond * 10;
            while (this.RunState != State.Started)
            {
                Thread.Sleep(100);
                if (DateTime.Now.Ticks > waitTime)
                {
                    throw new TimeoutException("Unable to start the request handling process.");
                }
            }
        }

        public virtual void Stop()
        {
            // Setting the runstate to something other than "started" and
            // stopping the listener should abort the AddIncomingRequestToQueue
            // method and allow the ConnectionManagerThreadStart sequence to
            // end, which sets the RunState to Stopped.
            Interlocked.Exchange(ref this._runState, (long)State.Stopping);
            if (this.httpListener.IsListening)
            {
                this.httpListener.Stop();
            }
            long waitTime = DateTime.Now.Ticks + TimeSpan.TicksPerSecond * 10;
            while (this.RunState != State.Stopped)
            {
                Thread.Sleep(100);
                if (DateTime.Now.Ticks > waitTime)
                {
                    throw new TimeoutException("Unable to stop the web server process.");
                }
            }

            this._connectionManagerThread = null;
        }

        public virtual void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }

        private void Dispose(bool disposing)
        {
            if (this._disposed)
            {
                return;
            }
            if (disposing)
            {
                if (this.RunState != State.Stopped)
                {
                    this.Stop();
                }
                if (this._connectionManagerThread != null)
                {
                    this._connectionManagerThread.Abort();
                    this._connectionManagerThread = null;
                }
            }
            this._disposed = true;
        }

        private void RaiseIncomingRequest(HttpListenerContext context)
        {
            HttpHandler e = new HttpHandler(context);
            try
            {
                if (this.IncomingRequest != null)
                {
                    this.IncomingRequest.BeginInvoke(this, e, null, null);
                }
            }
            catch
            {
                // Swallow the exception and/or log it, but you probably don't want to exit
                // just because an incoming request handler failed.
            }
        }

        private void ConnectionManagerThreadStart()
        {
            Interlocked.Exchange(ref this._runState, (long)State.Starting);
            try
            {
                if (!this.httpListener.IsListening)
                {
                    this.httpListener.Start();
                }
                if (this.httpListener.IsListening)
                {
                    Interlocked.Exchange(ref this._runState, (long)State.Started);
                }

                try
                {
                    while (RunState == State.Started)
                    {
                        HttpListenerContext context = this.httpListener.GetContext();
                        this.RaiseIncomingRequest(context);
                    }
                }
                catch (HttpListenerException exception)
                {
                    throw new Exception(exception.ToString());
                    // This will occur when the listener gets shut down.
                    // Just swallow it and move on.
                }
            }
            finally
            {
                Interlocked.Exchange(ref this._runState, (long)State.Stopped);
            }
        }

        private Thread _connectionManagerThread = null;
        private bool _disposed = false;
        private long _runState = (long)State.Stopped;
        private readonly HttpListener httpListener;   
    }
}
